-use std::collections::HashMap;
+use std::default::Default;
use std::io::fs::{mod, PathExtensions};
use core::{MultiShell, PackageSet};
let pkgs = PackageSet::new([]);
let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg,
Layout::at(root.get_absolute_target_dir()),
- None, &pkg, HashMap::new()));
+ None, &pkg, Default::default()));
// And finally, clean everything out!
for target in pkg.get_targets().iter() {
use std::os;
use std::collections::HashMap;
+use std::default::Default;
use core::registry::PackageRegistry;
use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId};
let ret = {
let _p = profile::start("compiling");
- let lib_overrides = try!(scrape_target_config(&config, &user_configs));
+ let lib_overrides = try!(scrape_build_config(&config, &user_configs));
try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build,
&PackageSet::new(packages.as_slice()),
}).map(|p| SourceId::for_path(&p)).collect()
}
-fn scrape_target_config(config: &Config,
- configs: &HashMap<String, config::ConfigValue>)
- -> CargoResult<HashMap<String, BuildOutput>> {
+fn scrape_build_config(config: &Config,
+ configs: &HashMap<String, config::ConfigValue>)
+ -> CargoResult<ops::BuildConfig> {
let target = match configs.find_equiv("target") {
- None => return Ok(HashMap::new()),
+ None => return Ok(Default::default()),
Some(target) => try!(target.table().chain_error(|| {
internal("invalid configuration for the key `target`")
})),
};
- let triple = config.target().unwrap_or(config.rustc_host()).to_string();
- let target = match target.get(&triple) {
- None => return Ok(HashMap::new()),
+
+ let host = try!(scrape_target_config(target, config.rustc_host()));
+ let target = match config.target() {
+ Some(triple) => try!(scrape_target_config(target, triple)),
+ None => host.clone(),
+ };
+ Ok(ops::BuildConfig { host: host, target: target })
+}
+
+fn scrape_target_config(target: &HashMap<String, config::ConfigValue>,
+ triple: &str)
+ -> CargoResult<ops::TargetConfig> {
+ let target = match target.get(&triple.to_string()) {
+ None => return Ok(Default::default()),
Some(target) => try!(target.table().chain_error(|| {
internal(format!("invalid configuration for the key \
`target.{}`", triple))
})),
};
- let mut ret = HashMap::new();
+ let mut ret = ops::TargetConfig {
+ ar: None,
+ linker: None,
+ overrides: HashMap::new(),
+ };
for (k, v) in target.iter() {
match k.as_slice() {
"ar" | "linker" => {
internal(format!("invalid configuration for key `{}`", k))
})).ref0().to_string();
if k.as_slice() == "linker" {
- config.set_linker(v);
+ ret.linker = Some(v);
} else {
- config.set_ar(v);
+ ret.ar = Some(v);
}
}
lib_name => {
output.metadata.push((k.to_string(), v.to_string()));
}
}
- ret.insert(lib_name.to_string(), output);
+ ret.overrides.insert(lib_name.to_string(), output);
}
}
}
use util::{mod, CargoResult, ChainError, internal, Config, profile};
use util::human;
-use super::{Kind, KindHost, KindTarget, Compilation, BuildOutput};
+use super::{Kind, KindHost, KindTarget, Compilation, BuildConfig};
+use super::TargetConfig;
use super::layout::{Layout, LayoutProxy};
use super::custom_build::BuildState;
target_dylib: Option<(String, String)>,
target_exe: String,
requirements: HashMap<(&'a PackageId, &'a str), PlatformRequirement>,
+ build_config: BuildConfig,
}
impl<'a, 'b: 'a> Context<'a, 'b> {
deps: &'a PackageSet, config: &'b Config<'b>,
host: Layout, target: Option<Layout>,
root_pkg: &Package,
- build_state: HashMap<String, BuildOutput>)
+ build_config: BuildConfig)
-> CargoResult<Context<'a, 'b>> {
let (target_dylib, target_exe) =
try!(Context::filename_parts(config.target()));
host_dylib: host_dylib,
requirements: HashMap::new(),
compilation: Compilation::new(root_pkg),
- build_state: Arc::new(BuildState::new(build_state, deps)),
+ build_state: Arc::new(BuildState::new(build_config.clone(), deps)),
+ build_config: build_config,
})
}
pub fn get_requirement(&self, pkg: &'a Package,
target: &'a Target) -> PlatformRequirement {
+ let default = if target.get_profile().is_for_host() {
+ PlatformPlugin
+ } else {
+ PlatformTarget
+ };
self.requirements.get(&(pkg.get_package_id(), target.get_name()))
- .map(|a| *a).unwrap_or(PlatformTarget)
+ .map(|a| *a).unwrap_or(default)
}
/// Returns the appropriate directory layout for either a plugin or not.
!target.get_profile().is_test(),
}
}
+
+ /// Get the user-specified linker for a particular host or target
+ pub fn linker(&self, kind: Kind) -> Option<&str> {
+ self.target_config(kind).linker.as_ref().map(|s| s.as_slice())
+ }
+
+ /// Get the user-specified `ar` program for a particular host or target
+ pub fn ar(&self, kind: Kind) -> Option<&str> {
+ self.target_config(kind).ar.as_ref().map(|s| s.as_slice())
+ }
+
+ /// Get the target configuration for a particular host or target
+ fn target_config(&self, kind: Kind) -> &TargetConfig {
+ match kind {
+ KindHost => &self.build_config.host,
+ KindTarget => &self.build_config.target,
+ }
+ }
}
impl PlatformRequirement {
use util::{internal, ChainError, Require};
use super::job::Work;
-use super::{fingerprint, process, KindTarget, KindHost, Context};
+use super::{fingerprint, process, KindTarget, KindHost, Kind, Context};
use util::Freshness;
/// Contains the parsed output of a custom build script.
}
pub struct BuildState {
- pub outputs: Mutex<HashMap<PackageId, BuildOutput>>,
+ pub outputs: Mutex<HashMap<(PackageId, Kind), BuildOutput>>,
}
/// Prepares a `Work` that executes the target as a custom build script.
-pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
+pub fn prepare(pkg: &Package, target: &Target, kind: Kind, cx: &mut Context)
-> CargoResult<(Work, Work, Freshness)> {
// TODO: this shouldn't explicitly pass `KindTarget` for the layout, we
// may be running a build script for a plugin dependency.
let (script_output, old_script_output, build_output, old_build_output) = {
- let target = cx.layout(pkg, KindTarget);
+ let target = cx.layout(pkg, kind);
let host = cx.layout(pkg, KindHost);
(host.build(pkg),
host.proxy().old_build(pkg),
.dir_path()
.display().to_string()))
.env("NUM_JOBS", Some(cx.config.jobs().to_string()))
- .env("TARGET", Some(cx.target_triple()))
+ .env("TARGET", Some(match kind {
+ KindHost => cx.config.rustc_host(),
+ KindTarget => cx.target_triple(),
+ }))
.env("DEBUG", Some(profile.get_debug().to_string()))
.env("OPT_LEVEL", Some(profile.get_opt_level().to_string()))
.env("PROFILE", Some(profile.get_env()));
{
let build_state = build_state.outputs.lock();
for &(ref name, ref id) in lib_deps.iter() {
- for &(ref key, ref value) in (*build_state)[*id].metadata.iter() {
+ let data = &build_state[(id.clone(), kind)].metadata;
+ for &(ref key, ref value) in data.iter() {
p = p.env(format!("DEP_{}_{}",
super::envify(name.as_slice()),
super::envify(key.as_slice())).as_slice(),
human("build script output was not valid utf-8")
}));
let build_output = try!(BuildOutput::parse(output, pkg_name.as_slice()));
- build_state.outputs.lock().insert(id, build_output);
+ build_state.outputs.lock().insert((id, kind), build_output);
try!(File::create(&script_output.join("output"))
.write_str(output).map_err(|e| {
let contents = try!(f.read_to_string());
let output = try!(BuildOutput::parse(contents.as_slice(),
pkg_name.as_slice()));
- build_state.outputs.lock().insert(id, output);
+ build_state.outputs.lock().insert((id, kind), output);
fresh(tx)
};
}
impl BuildState {
- pub fn new(overrides: HashMap<String, BuildOutput>,
+ pub fn new(config: super::BuildConfig,
packages: &PackageSet) -> BuildState {
let mut sources = HashMap::new();
for package in packages.iter() {
}
}
let mut outputs = HashMap::new();
- for (name, output) in overrides.into_iter() {
- outputs.insert(sources[name].clone(), output);
+ for (name, output) in config.host.overrides.into_iter() {
+ outputs.insert((sources[name].clone(), KindHost), output);
+ }
+ for (name, output) in config.target.overrides.into_iter() {
+ outputs.insert((sources[name].clone(), KindTarget), output);
}
BuildState { outputs: Mutex::new(outputs) }
}
mod layout;
mod links;
-#[deriving(PartialEq, Eq)]
+#[deriving(PartialEq, Eq, Hash, Show)]
pub enum Kind { KindHost, KindTarget }
+#[deriving(Default, Clone)]
+pub struct BuildConfig {
+ pub host: TargetConfig,
+ pub target: TargetConfig,
+}
+
+#[deriving(Clone, Default)]
+pub struct TargetConfig {
+ pub ar: Option<String>,
+ pub linker: Option<String>,
+ pub overrides: HashMap<String, BuildOutput>,
+}
+
/// Run `rustc` to figure out what its current version string is.
///
/// The second element of the tuple returned is the target triple that rustc
deps: &PackageSet, resolve: &'a Resolve,
sources: &'a SourceMap,
config: &'a Config<'a>,
- lib_overrides: HashMap<String, BuildOutput>)
+ build_config: BuildConfig)
-> CargoResult<Compilation> {
if targets.is_empty() {
return Ok(Compilation::new(pkg))
let mut cx = try!(Context::new(env, resolve, sources, deps, config,
host_layout, target_layout, pkg,
- lib_overrides));
+ build_config));
let mut queue = JobQueue::new(cx.resolve, deps, cx.config);
// First ensure that the destination directory exists
(Vec::new(), Vec::new(), Vec::new(), Vec::new());
let (mut build_custom, mut run_custom) = (Vec::new(), Vec::new());
for &target in targets.iter() {
- if target.get_profile().is_custom_build() {
- // Custom build commands that are for libs that are overridden are
- // skipped entirely
- if pkg.get_manifest().get_links().is_some() &&
- cx.build_state.outputs.lock().contains_key(pkg.get_package_id()) {
- continue
- }
- let (dirty, fresh, freshness) =
- try!(custom_build::prepare(pkg, target, cx));
- run_custom.push((job(dirty, fresh), freshness));
- }
-
let work = if target.get_profile().is_doc() {
let rustdoc = try!(rustdoc(pkg, target, cx));
vec![(rustdoc, KindTarget)]
try!(rustc(pkg, target, cx, req))
};
+ // Figure out what stage this work will go into
let dst = match (target.is_lib(),
target.get_profile().is_test(),
target.get_profile().is_custom_build()) {
};
dst.push((job(dirty, fresh), freshness));
}
+
+ // If this is a custom build command, we need to not only build the
+ // script but we also need to run it. Note that this is a little nuanced
+ // because we may need to run the build script multiple times. If the
+ // package is needed in both a host and target context, we need to run
+ // it once per context.
+ if !target.get_profile().is_custom_build() { continue }
+ let mut kinds = Vec::new();
+ let requirement = targets.iter().find(|t| {
+ !t.get_profile().is_custom_build() && !t.get_profile().is_doc()
+ }).map(|&other_target| {
+ cx.get_requirement(pkg, other_target)
+ }).unwrap_or(PlatformTarget);
+ match requirement {
+ PlatformTarget => kinds.push(KindTarget),
+ PlatformPlugin => kinds.push(KindHost),
+ PlatformPluginAndTarget => {
+ kinds.push(KindTarget);
+ if cx.config.target().is_some() {
+ kinds.push(KindHost);
+ }
+ }
+ }
+ let before = run_custom.len();
+ for &kind in kinds.iter() {
+ let key = (pkg.get_package_id().clone(), kind);
+ if pkg.get_manifest().get_links().is_some() &&
+ cx.build_state.outputs.lock().contains_key(&key) {
+ continue
+ }
+ let (dirty, fresh, freshness) =
+ try!(custom_build::prepare(pkg, target, kind, cx));
+ run_custom.push((job(dirty, fresh), freshness));
+ }
+
+ // If no build scripts were run, no need to compile the build script!
+ if run_custom.len() == before {
+ dst.pop();
+ }
}
if targets.iter().any(|t| t.get_profile().is_custom_build()) {
// Prepare the native lib state (extra -L and -l flags)
let build_state = cx.build_state.clone();
- let mut native_lib_deps = Vec::new();
+ let mut native_lib_deps = HashSet::new();
let current_id = package.get_package_id().clone();
let has_custom_build = package.get_targets().iter().any(|t| {
t.get_profile().is_custom_build()
});
- // FIXME: traverse build dependencies and add -L and -l for an
- // transitive build deps.
- if !target.get_profile().is_custom_build() {
- each_dep(package, cx, |dep| {
- if dep.get_manifest().get_links().is_some() ||
- (*dep.get_package_id() == current_id && has_custom_build) {
- native_lib_deps.push(dep.get_package_id().clone());
+ if has_custom_build && !target.get_profile().is_custom_build() {
+ native_lib_deps.insert(current_id.clone());
+ }
+ // Visit dependencies transitively to figure out what our native
+ // dependencies are (for -L and -l flags).
+ for &(pkg, _) in cx.dep_targets(package, target).iter() {
+ each_dep(pkg, cx, |dep| {
+ let has_custom_build = dep.get_targets().iter().any(|t| {
+ t.get_profile().is_custom_build()
+ });
+ if has_custom_build {
+ native_lib_deps.insert(dep.get_package_id().clone());
}
});
}
+ let mut native_lib_deps = native_lib_deps.into_iter().collect::<Vec<_>>();
+ native_lib_deps.sort();
(proc(desc_tx: Sender<String>) {
let mut rustc = rustc;
// arguments are for native libraries, so we process those here.
{
let build_state = build_state.outputs.lock();
- for id in native_lib_deps.iter() {
- let output = &(*build_state)[*id];
+ for id in native_lib_deps.into_iter() {
+ let output = &build_state[(id.clone(), kind)];
for path in output.library_paths.iter() {
rustc = rustc.arg("-L").arg(path);
}
- if *id == current_id {
+ if id == current_id {
for name in output.library_links.iter() {
rustc = rustc.arg("-l").arg(name.as_slice());
}
}
cmd = opt(cmd, "--target", "", cx.config.target());
- cmd = opt(cmd, "-C", "ar=", cx.config.ar().as_ref()
- .map(|s| s.as_slice()));
- cmd = opt(cmd, "-C", "linker=", cx.config.linker().as_ref()
- .map(|s| s.as_slice()));
+ cmd = opt(cmd, "-C", "ar=", cx.ar(kind));
+ cmd = opt(cmd, "-C", "linker=", cx.linker(kind));
}
return cmd;
pub use self::cargo_rustc::{KindTarget, KindHost, Context, LayoutProxy};
pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
-pub use self::cargo_rustc::{BuildOutput};
+pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig};
pub use self::cargo_run::run;
pub use self::cargo_new::{new, NewOptions};
pub use self::cargo_doc::{doc, DocOptions};
use std::{fmt, os, mem};
-use std::cell::{RefCell, RefMut, Ref};
+use std::cell::{RefCell, RefMut};
use std::collections::hash_map::{HashMap, Occupied, Vacant};
use std::io::fs::{PathExtensions, File};
use std::string;
shell: RefCell<&'a mut MultiShell>,
jobs: uint,
target: Option<string::String>,
- linker: RefCell<Option<string::String>>,
- ar: RefCell<Option<string::String>>,
rustc_version: string::String,
/// The current host and default target of rustc
rustc_host: string::String,
shell: RefCell::new(shell),
jobs: jobs.unwrap_or(os::num_cpus()),
target: target,
- ar: RefCell::new(None),
- linker: RefCell::new(None),
rustc_version: rustc_version,
rustc_host: rustc_host,
})
self.target.as_ref().map(|t| t.as_slice())
}
- pub fn set_ar(&self, ar: string::String) {
- *self.ar.borrow_mut() = Some(ar);
- }
-
- pub fn set_linker(&self, linker: string::String) {
- *self.linker.borrow_mut() = Some(linker);
- }
-
- pub fn linker(&self) -> Ref<Option<string::String>> { self.linker.borrow() }
- pub fn ar(&self) -> Ref<Option<string::String>> { self.ar.borrow() }
-
/// Return the output of `rustc -v verbose`
pub fn rustc_version(&self) -> &str {
self.rustc_version.as_slice()
Example output:
```
-cargo:rustc-flags=-l static:foo -L /path/to/foo
+cargo:rustc-flags=-l foo:static -L /path/to/foo
cargo:root=/path/to/foo
cargo:libdir=/path/to/foo/lib
cargo:include=/path/to/foo/include
{running} `[..]a-[..]build-script-build[..]`
{running} `rustc [..] --crate-name a [..]-L bar[..]-L foo[..]`
{compiling} foo v0.5.0 (file://[..])
-{running} `rustc [..] --crate-name foo [..] -L bar[..]-L foo[..]`
+{running} `rustc [..] --crate-name foo [..] -L bar -L foo`
", compiling = COMPILING, running = RUNNING).as_slice()));
})
use support::{RUNNING, COMPILING, DOCTEST, cargo_dir};
use hamcrest::{assert_that, existing_file};
use cargo::util::process;
+use cargo::ops::rustc_version;
fn setup() {
}
dir = p.root().display(), sep = path::SEP).as_slice()));
})
+test!(build_script_needed_for_host_and_target {
+ if disabled() { return }
+
+ let target = alternate();
+ let (_, host) = rustc_version().unwrap();
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+
+ [dependencies.d1]
+ path = "d1"
+ [build-dependencies.d2]
+ path = "d2"
+ "#)
+
+ .file("build.rs", r#"
+ extern crate d2;
+ fn main() { d2::d2(); }
+ "#)
+ .file("src/main.rs", "
+ extern crate d1;
+ fn main() { d1::d1(); }
+ ")
+ .file("d1/Cargo.toml", r#"
+ [package]
+ name = "d1"
+ version = "0.0.0"
+ authors = []
+ build = 'build.rs'
+ "#)
+ .file("d1/src/lib.rs", "
+ pub fn d1() {}
+ ")
+ .file("d1/build.rs", r#"
+ use std::os;
+ fn main() {
+ let target = os::getenv("TARGET").unwrap();
+ println!("cargo:rustc-flags=-L /path/to/{}", target);
+ }
+ "#)
+ .file("d2/Cargo.toml", r#"
+ [package]
+ name = "d2"
+ version = "0.0.0"
+ authors = []
+
+ [dependencies.d1]
+ path = "../d1"
+ "#)
+ .file("d2/src/lib.rs", "
+ extern crate d1;
+ pub fn d2() { d1::d1(); }
+ ");
+
+ assert_that(p.cargo_process("build").arg("--target").arg(&target).arg("-v"),
+ execs().with_status(0)
+ .with_stdout(format!("\
+{compiling} d1 v0.0.0 (file://{dir})
+{running} `rustc build.rs [..] --out-dir {dir}{sep}target{sep}build{sep}d1-[..]`
+{running} `{dir}{sep}target{sep}build{sep}d1-[..]build-script-build`
+{running} `{dir}{sep}target{sep}build{sep}d1-[..]build-script-build`
+{running} `rustc {dir}{sep}d1{sep}src{sep}lib.rs [..] --target {target} [..] \
+ -L /path/to/{target}`
+{running} `rustc {dir}{sep}d1{sep}src{sep}lib.rs [..] \
+ -L /path/to/{host}`
+{compiling} d2 v0.0.0 (file://{dir})
+{running} `rustc {dir}{sep}d2{sep}src{sep}lib.rs [..] \
+ -L /path/to/{host}`
+{compiling} foo v0.0.0 (file://{dir})
+{running} `rustc build.rs [..] --out-dir {dir}{sep}target{sep}build{sep}foo-[..] \
+ -L /path/to/{host}`
+{running} `{dir}{sep}target{sep}build{sep}foo-[..]build-script-build`
+{running} `rustc {dir}{sep}src{sep}main.rs [..] --target {target} [..] \
+ -L /path/to/{target}`
+", compiling = COMPILING, running = RUNNING, target = target, host = host,
+ dir = p.root().display(), sep = path::SEP).as_slice()));
+})